<?php

namespace Server;
use App\process\ChatClear;
use Consts\RedisKey;
use Elasticsearch\Endpoints\Ml\Validate;
use Helper\Config;
use Helper\Context;
use Helper\Output;
use Helper\MagicCrypt;
use Helper\Mysql;
use Helper\Redis;
use Helper\Redis1;
use Helper\Reg;
use Middleware\AuthMiddleware;
use Middleware\CustomerMiddleware;
use Middleware\ServiceMiddleware;
use Middleware\SignValidationMiddleware;
use Middleware\TokenValidationMiddleware;
use Swoole\Process;

class WebsocketServer
{

    public $server;
    public function __construct()
    {


        $serverConf = Config::getInstance()->get('server');
        cli_set_process_title($serverConf['server_name']);
        $this->server = new \Swoole\WebSocket\Server($serverConf['host'], $serverConf['port']);
        $this->server->set($serverConf['setting']);
        $this->server->on('handshake',$this->handshakeCallBack());
        $this->server->on('message',$this->messageCallBack());
        $this->server->on('close', $this->closeCallBack());
        $this->server->on('request', $this->requestCallBack());
        $this->server->on('Shutdown',$this->shutdownCallBack());
        $this->server->on('Start', $this->startCallBack());
        $process = new Process($this->proceCallBack('ChatClear') ,false,2,1);
        $process->start();

        if(ENV == "dev"){
            $process = new Process($this->proceCallBack('HotReload') ,false,2,1);
            $process->start();
        }


        Reg::getInstance()->set('server',$this->server);

    }

    /**
     * 握手
     * @return \Closure
     */
    public function handshakeCallBack(){
        $callback =  function (\Swoole\Http\Request $request, \Swoole\Http\Response $response){

            $role = 1;
            $userType = TokenValidationMiddleware::getInstance()->authWs($request,$response);
            if(!$userType){
                $response->status(500);
                $response->end();
                return  false;
            }
            if(isset($userType->user)){

                //验证是不是最新token
                $serverToken = Redis::getInstance()->get(RedisKey::loginUserToken($userType->user_id));
                if(!$serverToken){

                    return false;
                }
                if($serverToken != $request->get['token']){

                    return false;
                }
                //客服
                $role = 2;
                ServiceMiddleware::getInstance()->online($userType->user , $request->fd);
            }else{
                //客户
                CustomerMiddleware::getInstance()->online($userType->customer_id,$request->fd);

            }

            // websocket握手连接算法验证
            $secWebSocketKey = $request->header['sec-websocket-key'];
            $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
            if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
                $response->end();
                return false;
            }
//            echo $request->header['sec-websocket-key'];
            $key = base64_encode(
                sha1(
                    $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
                    true
                )
            );

            $headers = [
                'Upgrade' => 'websocket',
                'Connection' => 'Upgrade',
                'Sec-WebSocket-Accept' => $key,
                'Sec-WebSocket-Version' => '13',
            ];

            if (isset($request->header['sec-websocket-protocol'])) {
                $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
            }

            foreach ($headers as $key => $val) {
                $response->header($key, $val);
            }

            $response->status(101);

            //标记当前FD是客服还是用户
            Redis::getInstance()->hSet(RedisKey::FD_ROLE,"fd:".$request->fd , $role);

            $response->end();

        };
        return $callback;
    }

    public function messageCallBack(){

        $callback =  function (\Swoole\WebSocket\Server $server, $frame){

            //根据数据包决定分发到某个控制器处理
            if(empty($frame->data)){
                $server->push($frame->fd,Output::json(1001));
                return ;
            }
            $pack = json_decode($frame->data,true);
            if(!is_array($pack)){
                $server->push($frame->fd,Output::json(1000));
                return ;
            }

            $controller  = "App\\controllers\\ws\\".ucfirst( $pack['class']);
            $action = $pack['action'];
            $param  = $pack['data'];

            (new $controller)->$action($server , $frame->fd ,$param);

        };
        return $callback;
    }

    public function closeCallBack(){

        $callback =  function ($server, $fd,  $reactorId){

//            var_dump('reactorId1:'.$reactorId);
            $role = Redis::getInstance()->hGet(RedisKey::FD_ROLE,"fd:".$fd );
            if($role == 1){
                //处理用户断开逻辑
//                var_dump('用户断开fd:'.$fd);
                CustomerMiddleware::getInstance()->offline($fd);
                Redis::getInstance()->hDel(RedisKey::FD_ROLE,"fd:".$fd );

            }elseif($role == 2){
                //处理客服断开逻辑
//                var_dump('客服断开fd:'.$fd);
                ServiceMiddleware::getInstance()->offline($fd);
                Redis::getInstance()->hDel(RedisKey::FD_ROLE,"fd:".$fd );
            }else{

//                var_dump('http 响应结束！');
            }



        };
        return $callback;
    }


    /**
     * 请求回调
     * @return \Closure
     */
    public function requestCallBack(){

        $callback =  function ($request, $response){

            register_shutdown_function(function () use ($response) {
                $error = error_get_last();

                switch ($error['type'] ?? null) {
                    case E_ERROR :
                    case E_PARSE :
                    case E_CORE_ERROR :
                    case E_COMPILE_ERROR :
                        // log or send:
                        // error_log($message);
                        // $server->send($fd, $error['message']);
                        $response->status(500);
                        $response->end($error['message']);
                        break;
                }
            });
            Context::put('response',$response);
//            Output::$response = $response;

            $response->header('Access-Control-Allow-Origin', '*');
            Reg::getInstance()->set('server',$this->server);

            $uri = explode('/',$request->server['request_uri']);

            //验证token
            $tokenAuth = TokenValidationMiddleware::getInstance()->tokenAuth($request, $response);
            if(!$tokenAuth){
                return false;
            }
            //签名
            $signAuth = SignValidationMiddleware::getInstance()->signAuth($request, $response);
            if(!$signAuth){
                return false;
            }
            //参数验证
            $Valid  =  \Helper\Validate::check($request->server['request_uri'],$request->post);
            if($Valid->isFail()){
                return  Output::responseJson(500 ,$Valid->firstError());
            }
            $request->post = $Valid->getSafeData();
            if($uri[1] != 'v1'){
                //验证权限菜单
                $menuAuth = AuthMiddleware::getInstance()->menuAuth($request, $response);
                if(!$menuAuth){
                    return false;
                }

            }else{
                $apiAuth = AuthMiddleware::getInstance()->apiAuth($request, $response);
                if(!$apiAuth){
                    return false;
                }
                if( $request->post['p']){

                    try {

                        $p = MagicCrypt::getInstance()->decrypt($request->post['p']);

                        if(empty($p)){
                            return  Output::responseJson(500,'参数p错误');
                        }
                        $request->post['p'] = $p;

                    }catch (\Exception $e){

                        return  Output::responseJson(500,'参数p错误');
                    }

                }
                $request->post['uid'] = Reg::getInstance()->get('customer_id');

            }



            list($module,$controller, $action) = explode('/', trim($request->server['request_uri'], '/'));
            $controller = "App\\controllers\\$module\\".ucfirst($controller);
            $res = (new $controller)->$action($request, $response);
            $response->header("Content-Type", "text/json; charset=utf-8");
            $response->end($res);

        };
        return $callback;
    }


    /**
     * 进程回调
     */
    public function proceCallBack($processName){

        $callback = function ($process ) use ($processName){
            $process->name($processName);
            $className =  'App\process\\'.$processName;
            if($processName == "HotReload"){
                $obj =  new  $className(Config::getInstance()->get('server')['setting']['document_root'], '/usr/bin/php service',['vendor']);
            }else{
                $obj =  new  $className;
            }



            $obj->start($this->server);

        };

        return $callback ;

    }


    public function shutdownCallBack(){

        $callback = function (\swoole_server $server){

            $data = [
                'msg' => ' shutdown !' ,
                'data_' => '1',
                'create_at' => time(),
            ];
            Mysql::getInstance()->insert('process_error_log',$data);
        };

        return $callback ;
    }

    public function startCallBack(){

        $callback = function (\Swoole\Server $server){




            go(function (){

                Redis::getInstance()->flushDB();

                Redis::getInstance()->set(RedisKey::KF_PLATFORM_LIST,json_encode(array_column(Mysql::getInstance()->where('state',1)->get('platform'),'id')));
                $svs = Mysql::getInstance()->get('admin_user');
                foreach ($svs as $item){
                    Redis::getInstance()->hSet(RedisKey::ADMIN_SERVICE_INFO_LIST,'id:'.$item['id'],json_encode($item));
                }

            });
        };

        return $callback ;
    }

    public function start(){

        Output::string( Output::getRunLogo() );

        $this->server->start();


    }

}
